Skip to content

feat: add unenroll_learners management command for bulk unenrollment#3603

Merged
Anas12091101 merged 5 commits into
mainfrom
anas/add-bulk-unenroll
Jun 4, 2026
Merged

feat: add unenroll_learners management command for bulk unenrollment#3603
Anas12091101 merged 5 commits into
mainfrom
anas/add-bulk-unenroll

Conversation

@Anas12091101

@Anas12091101 Anas12091101 commented May 22, 2026

Copy link
Copy Markdown
Contributor

What are the relevant tickets?

https://github.com/mitodl/hq/issues/11347

Description

Add a new unenroll_learners management command that supports bulk unenrollment of learners from course runs in both edX and MITx Online.

Input modes:

  • --csv — CSV file with user and courseware_id columns
  • --users with --run — Comma-separated user emails/usernames for a specific course run
  • --run alone — Unenroll ALL active learners from a course run

Safety & options:

  • Defaults to dry-run mode (preview only); requires --commit to execute
  • --no-email suppresses unenrollment notification emails
  • -k keeps local enrollment records even if edX unenrollment fails

The core unenrollment logic is extracted into shared utility functions (unenroll_learner_from_run and bulk_unenroll_learners) in courses/management/utils.py, which handle user/run resolution, enrollment deactivation via deactivate_run_enrollment, and structured logging. A send_notification parameter was added to deactivate_run_enrollment in courses/api.py to support email suppression.

The previous single-user unenroll_enrollment command has been removed since unenroll_learners covers both single and bulk use cases.

How to test

1. Dry-run (default — no changes made):

./manage.py unenroll_learners --users=user@example.com --run=course-v1:MITx+6.00.1x+2024

Verify output shows (DRY RUN), lists "Would unenroll" entries, and prints "Re-run with --commit to apply changes."

2. Unenroll all active learners from a course run (preview):

./manage.py unenroll_learners --run=course-v1:MITx+6.00.1x+2024

Verify it lists ALL active enrollments for that run in dry-run mode.

3. Unenroll specific users with --commit:

./manage.py unenroll_learners --users=user1@example.com,user2@example.com --run=course-v1:MITx+6.00.1x+2024 --commit

Verify users are unenrolled from both edX and MITx Online, and summary shows succeeded count.

4. Unenroll via CSV with --commit:

./manage.py unenroll_learners --csv=unenrollments.csv --commit

CSV format: user,courseware_id columns. Verify all listed users are unenrolled.

5. Unenroll all active learners from a run with --commit:

./manage.py unenroll_learners --run=course-v1:MITx+6.00.1x+2024 --commit

Verify ALL active enrollments for that run are unenrolled.

6. Suppress unenrollment emails (--no-email):

./manage.py unenroll_learners --users=user@example.com --run=course-v1:MITx+6.00.1x+2024 --commit --no-email

Verify unenrollment succeeds and no notification email is sent to the learner.

7. Keep failed enrollments (-k):

./manage.py unenroll_learners --users=user@example.com --run=course-v1:MITx+6.00.1x+2024 --commit -k

Verify local enrollment record is preserved even if edX API call fails.

8. Conflicting arguments are rejected:

./manage.py unenroll_learners --csv=file.csv --users=user@example.com
./manage.py unenroll_learners --csv=file.csv --run=course-v1:MITx+6.00.1x+2024
./manage.py unenroll_learners --users=user@example.com
./manage.py unenroll_learners

Verify each raises a clear error message.

9. Run automated tests:

docker compose run --rm -e WEBPACK_LOADER_ENABLED=False web pytest unenroll_learners_test.py -v

All 26 tests should pass.

Additional Context

@github-actions

Copy link
Copy Markdown

OpenAPI Changes

Show/hide ## Changes for v0.yaml:
## Changes for v0.yaml:
No changes detected

## Changes for v1.yaml:
No changes detected

## Changes for v2.yaml:
No changes detected

Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

@Anas12091101 Anas12091101 force-pushed the anas/add-bulk-unenroll branch from df36094 to 163556a Compare May 22, 2026 13:36
@jkachel

jkachel commented May 22, 2026

Copy link
Copy Markdown
Contributor

Chiming in briefly on this - not sure if it's ready for review yet or not since tests are failing. But there's two things that I think would be helpful:

  • Ability to tag a courseware ID for unenroll: the CSV format is an improvement but it'd also be nice to just be able to tag a particular course run. (I had to remove pretty much everyone from a handful of course runs; I could pull the enrollments into a CSV file but it'd been easier to just specify the runs.)
  • Ability to suppress the unenrollment email. Especially in the case that I was dealing with, we may not want to send out a bunch of (potentially confusing) email to people telling them they're unenrolled from a course that wasn't valid anyway.

A potential third:

  • Default to dry-run - in other words, preview first, require a flag to make changes, especially for the bulk moves so we're forced to be mindful of what we're doing.

I did read through the code and it looked pretty good but I haven't done a formal review at the moment.

@Anas12091101

Copy link
Copy Markdown
Contributor Author

@jkachel, thanks for the suggestions. I’ve implemented all three of them, and this PR is now ready for review.

@Anas12091101 Anas12091101 requested a review from jkachel May 22, 2026 16:36
@jkachel jkachel self-assigned this Jun 2, 2026

@jkachel jkachel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍 - one thing to change, which is just moving a test into another folder; as long as it still runs/passes without further changes (which it should), doing this shouldn't require a re-review. Otherwise looks good and works well.

@@ -0,0 +1,473 @@
"""Tests for unenroll_learners management command"""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: move this to courses/management/tests - otherwise, this shows up as a runnable management command.

@Anas12091101 Anas12091101 force-pushed the anas/add-bulk-unenroll branch from 0676056 to a1c0042 Compare June 3, 2026 10:10
@Anas12091101 Anas12091101 merged commit b519c3d into main Jun 4, 2026
9 checks passed
@Anas12091101 Anas12091101 deleted the anas/add-bulk-unenroll branch June 4, 2026 10:38
@odlbot odlbot mentioned this pull request Jun 4, 2026
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants